App.require(["vector3"], function (vector3) {
    var Vector3 = vector3.Vector3;

    function Ring(resolution, radius, width) {
        this.name = "ring";
        var nv = 2 * (2 + 2) * resolution;
        this.vertices = new Float32Array(3 * nv);
        this.normals = new Float32Array(3 * nv);

        var angle;
        var step = 6.283185307179586476925286766559 / resolution;

        var vertexoffset = 0;
        var normaloffset = 0;
        for (var i = 0; i < resolution; i++) {
            angle = step * i;
            this.vertices[vertexoffset] = (radius - width / 2.0) * Math.cos(angle);
            this.vertices[vertexoffset + 1] = width / 2;
            this.vertices[vertexoffset + 2] = (radius - width / 2.0) * Math.sin(angle);

            this.normals[normaloffset] = -this.vertices[vertexoffset];
            this.normals[normaloffset + 1] = 0;
            this.normals[normaloffset + 2] = -this.vertices[vertexoffset + 2];
            vertexoffset += 3;
            normaloffset += 3;
        }

        for (var i = 0; i < resolution; i++) {
            angle = step * i;
            this.vertices[vertexoffset] = (radius + width / 2.0) * Math.cos(angle);
            this.vertices[vertexoffset + 1] = width / 2;
            this.vertices[vertexoffset + 2] = (radius + width / 2.0) * Math.sin(angle);

            this.normals[normaloffset] = this.vertices[vertexoffset];
            this.normals[normaloffset + 1] = 0;
            this.normals[normaloffset + 2] = this.vertices[vertexoffset + 2];

            vertexoffset += 3;
            normaloffset += 3;
        }

        for (var i = 0; i < resolution; i++) {
            angle = step * i;
            this.vertices[vertexoffset] = (radius - width / 2.0) * Math.cos(angle);
            this.vertices[vertexoffset + 1] = -width / 2;
            this.vertices[vertexoffset + 2] = (radius - width / 2.0) * Math.sin(angle);

            this.normals[normaloffset] = -this.vertices[vertexoffset];
            this.normals[normaloffset + 1] = 0;
            this.normals[normaloffset + 2] = -this.vertices[vertexoffset + 2];
            vertexoffset += 3;
            normaloffset += 3;
        }

        for (var i = 0; i < resolution; i++) {
            angle = step * i;
            this.vertices[vertexoffset] = (radius + width / 2.0) * Math.cos(angle);
            this.vertices[vertexoffset + 1] = -width / 2;
            this.vertices[vertexoffset + 2] = (radius + width / 2.0) * Math.sin(angle);

            this.normals[normaloffset] = this.vertices[vertexoffset];
            this.normals[normaloffset + 1] = 0;
            this.normals[normaloffset + 2] = this.vertices[vertexoffset + 2];

            vertexoffset += 3;
            normaloffset += 3;
        }

        var sidesV_offset = vertexoffset / 3;
        var sidesN_offset = normaloffset / 3;

        for (var i = 0; i < resolution; i++) {
            angle = step * i;
            this.vertices[vertexoffset] = (radius - width / 2.0) * Math.cos(angle);
            this.vertices[vertexoffset + 1] = width / 2;
            this.vertices[vertexoffset + 2] = (radius - width / 2.0) * Math.sin(angle);

            this.normals[normaloffset] = 0;
            this.normals[normaloffset + 1] = 1;
            this.normals[normaloffset + 2] = 0;
            vertexoffset += 3;
            normaloffset += 3;
        }

        for (var i = 0; i < resolution; i++) {
            angle = step * i;
            this.vertices[vertexoffset] = (radius + width / 2.0) * Math.cos(angle);
            this.vertices[vertexoffset + 1] = width / 2;
            this.vertices[vertexoffset + 2] = (radius + width / 2.0) * Math.sin(angle);

            this.normals[normaloffset] = 0;
            this.normals[normaloffset + 1] = 1;
            this.normals[normaloffset + 2] = 0;

            vertexoffset += 3;
            normaloffset += 3;
        }

        for (var i = 0; i < resolution; i++) {
            angle = step * i;
            this.vertices[vertexoffset] = (radius - width / 2.0) * Math.cos(angle);
            this.vertices[vertexoffset + 1] = -width / 2;
            this.vertices[vertexoffset + 2] = (radius - width / 2.0) * Math.sin(angle);

            this.normals[normaloffset] = 0;
            this.normals[normaloffset + 1] = -1;
            this.normals[normaloffset + 2] = 0;
            vertexoffset += 3;
            normaloffset += 3;
        }

        for (var i = 0; i < resolution; i++) {
            angle = step * i;
            this.vertices[vertexoffset] = (radius + width / 2.0) * Math.cos(angle);
            this.vertices[vertexoffset + 1] = -width / 2;
            this.vertices[vertexoffset + 2] = (radius + width / 2.0) * Math.sin(angle);

            this.normals[normaloffset] = 0;
            this.normals[normaloffset + 1] = -1;
            this.normals[normaloffset + 2] = 0;
            vertexoffset += 3;
            normaloffset += 3;
        }

        var nf = (2 + 2 + 2 + 2) * resolution;
        this.triangleIndices = new Uint16Array(3 * nf);

        var triangleoffset = 0;
        for (var i = 0; i < resolution; i++) {
            this.triangleIndices[triangleoffset] = sidesV_offset + i;
            this.triangleIndices[triangleoffset + 1] = sidesV_offset + i + resolution;
            this.triangleIndices[triangleoffset + 2] = sidesV_offset + (i + 1) % resolution;
            triangleoffset += 3;

            this.triangleIndices[triangleoffset] = sidesV_offset + (i + 1) % resolution;
            this.triangleIndices[triangleoffset + 1] = sidesV_offset + i + resolution;
            this.triangleIndices[triangleoffset + 2] = sidesV_offset + ((i + 1) % resolution) + resolution;
            triangleoffset += 3;

            this.triangleIndices[triangleoffset] = sidesV_offset + i + 2 * resolution;
            this.triangleIndices[triangleoffset + 1] = sidesV_offset + i + resolution + 2 * resolution;
            this.triangleIndices[triangleoffset + 2] = sidesV_offset + (i + 1) % resolution + 2 * resolution;
            triangleoffset += 3;

            this.triangleIndices[triangleoffset] = sidesV_offset + (i + 1) % resolution + 2 * resolution;
            this.triangleIndices[triangleoffset + 1] = sidesV_offset + i + resolution + 2 * resolution;
            this.triangleIndices[triangleoffset + 2] = sidesV_offset + ((i + 1) % resolution) + resolution + 2 * resolution;
            triangleoffset += 3;


            this.triangleIndices[triangleoffset] = i;
            this.triangleIndices[triangleoffset + 1] = (i + 1) % resolution;
            this.triangleIndices[triangleoffset + 2] = i + 2 * resolution;
            triangleoffset += 3;

            this.triangleIndices[triangleoffset] = i + 2 * resolution;
            this.triangleIndices[triangleoffset + 1] = (i + 1) % resolution;
            this.triangleIndices[triangleoffset + 2] = (i + 1) % resolution + 2 * resolution;
            triangleoffset += 3;

            this.triangleIndices[triangleoffset] = i + resolution;
            this.triangleIndices[triangleoffset + 1] = (i + 1) % resolution + resolution;
            this.triangleIndices[triangleoffset + 2] = i + 2 * resolution + resolution;
            triangleoffset += 3;

            this.triangleIndices[triangleoffset] = i + resolution + 2 * resolution;
            this.triangleIndices[triangleoffset + 1] = (i + 1) % resolution + resolution;
            this.triangleIndices[triangleoffset + 2] = (i + 1) % resolution + resolution + 2 * resolution;
            triangleoffset += 3;
        }

        this.numVertices = this.vertices.length / 3;
        this.numTriangles = this.triangleIndices.length / 3;
    }

    function Cylinder(resolution) {
        this.name = "cylinder";
        this.vertices = new Float32Array(2 * 3 * (2 * resolution + 2));
        this.normals = new Float32Array(2 * 3 * (2 * resolution + 2));

        var radius = 1.0;
        var angle;
        var step = 6.283185307179586476925286766559 / resolution;

        var vertexoffset = 0;
        var normaloffset = 0;
        for (var i = 0; i < resolution; i++) {
            angle = step * i;
            this.vertices[vertexoffset] = radius * Math.cos(angle);
            this.vertices[vertexoffset + 1] = 0.0;
            this.vertices[vertexoffset + 2] = radius * Math.sin(angle);

            this.normals[normaloffset] = this.vertices[vertexoffset];
            this.normals[normaloffset + 1] = 0;
            this.normals[normaloffset + 2] = this.vertices[vertexoffset + 2];

            vertexoffset += 3;
            normaloffset += 3;
        }

        for (var i = 0; i < resolution; i++) {
            angle = step * i;
            this.vertices[vertexoffset] = radius * Math.cos(angle);
            this.vertices[vertexoffset + 1] = 2.0;
            this.vertices[vertexoffset + 2] = radius * Math.sin(angle);

            this.normals[normaloffset] = this.vertices[vertexoffset];
            this.normals[normaloffset + 1] = 0;
            this.normals[normaloffset + 2] = this.vertices[vertexoffset + 2];

            vertexoffset += 3;
            normaloffset += 3;
        }

        this.vertices[vertexoffset] = 0.0;
        this.vertices[vertexoffset + 1] = 0.0;
        this.vertices[vertexoffset + 2] = 0.0;

        this.normals[normaloffset] = 0;
        this.normals[normaloffset + 1] = -1;
        this.normals[normaloffset + 2] = 0;

        vertexoffset += 3;
        normaloffset += 3;

        this.vertices[vertexoffset] = 0.0;
        this.vertices[vertexoffset + 1] = 2.0;
        this.vertices[vertexoffset + 2] = 0.0;

        this.normals[normaloffset] = 0;
        this.normals[normaloffset + 1] = 1;
        this.normals[normaloffset + 2] = 0;

        var sidesV_offset = vertexoffset / 3;
        var sidesN_offset = normaloffset / 3;

        for (var i = 0; i < resolution; i++) {
            angle = step * i;
            this.vertices[vertexoffset] = radius * Math.cos(angle);
            this.vertices[vertexoffset + 1] = 0.0;
            this.vertices[vertexoffset + 2] = radius * Math.sin(angle);

            this.normals[normaloffset] = 0;
            this.normals[normaloffset + 1] = -1;
            this.normals[normaloffset + 2] = 0;

            vertexoffset += 3;
            normaloffset += 3;
        }

        for (var i = 0; i < resolution; i++) {
            angle = step * i;

            this.vertices[vertexoffset] = radius * Math.cos(angle);
            this.vertices[vertexoffset + 1] = 2.0;
            this.vertices[vertexoffset + 2] = radius * Math.sin(angle);

            this.normals[normaloffset] = 0;
            this.normals[normaloffset + 1] = 1;
            this.normals[normaloffset + 2] = 0;

            vertexoffset += 3;
            normaloffset += 3;
        }

        this.vertices[vertexoffset] = 0.0;
        this.vertices[vertexoffset + 1] = 0.0;
        this.vertices[vertexoffset + 2] = 0.0;

        this.normals[normaloffset] = 0;
        this.normals[normaloffset + 1] = -1;
        this.normals[normaloffset + 2] = 0;

        vertexoffset += 3;
        normaloffset += 3;

        this.vertices[vertexoffset] = 0.0;
        this.vertices[vertexoffset + 1] = 2.0;
        this.vertices[vertexoffset + 2] = 0.0;

        this.normals[normaloffset] = 0;
        this.normals[normaloffset + 1] = 1;
        this.normals[normaloffset + 2] = 0;

        this.triangleIndices = new Uint16Array(3 * 4 * resolution);
        var triangleoffset = 0;
        for (var i = 0; i < resolution; i++) {
            this.triangleIndices[triangleoffset] = i;
            this.triangleIndices[triangleoffset + 1] = (i + 1) % resolution;
            this.triangleIndices[triangleoffset + 2] = (i % resolution) + resolution;
            triangleoffset += 3;

            this.triangleIndices[triangleoffset] = (i % resolution) + resolution;
            this.triangleIndices[triangleoffset + 1] = (i + 1) % resolution;
            this.triangleIndices[triangleoffset + 2] = ((i + 1) % resolution) + resolution;
            triangleoffset += 3;
        }

        for (var i = 0; i < resolution; i++) {
            this.triangleIndices[triangleoffset] = sidesV_offset + i;
            this.triangleIndices[triangleoffset + 1] = sidesV_offset + (i + 1) % resolution;
            this.triangleIndices[triangleoffset + 2] = sidesV_offset + 2 * resolution;
            triangleoffset += 3;
        }

        for (var i = 0; i < resolution; i++) {
            this.triangleIndices[triangleoffset] = sidesV_offset + resolution + i;
            this.triangleIndices[triangleoffset + 1] = sidesV_offset + ((i + 1) % resolution) + resolution;
            this.triangleIndices[triangleoffset + 2] = sidesV_offset + 2 * resolution + 1;
            triangleoffset += 3;
        }

        this.numVertices = this.vertices.length / 3;
        this.numTriangles = this.triangleIndices.length / 3;
    }

    function CircularStreet(resolution, width) {
        this.name = "circular street";
        this.vertices = new Float32Array(3 * 2 * resolution);

        var radius = 1000.0;
        var angle;
        var step = 6.283185307179586476925286766559 / resolution;
        var vertexoffset = 0;
        for (var i = 0; i < resolution; i++) {
            angle = step * i;
            this.vertices[vertexoffset] = (radius - width / 2.0) * Math.cos(angle);
            this.vertices[vertexoffset + 1] = 0.0;
            this.vertices[vertexoffset + 2] = (radius - width / 2.0) * Math.sin(angle);
            vertexoffset += 3;
        }

        for (var i = 0; i < resolution; i++) {
            angle = step * i;
            this.vertices[vertexoffset] = (radius + width / 2.0) * Math.cos(angle);
            this.vertices[vertexoffset + 1] = 0.0;
            this.vertices[vertexoffset + 2] = (radius + width / 2.0) * Math.sin(angle);
            vertexoffset += 3;
        }

        this.triangleIndices = new Uint16Array(3 * 2 * resolution);
        var triangleoffset = 0;
        for (var i = 0; i < resolution; i++) {
            this.triangleIndices[triangleoffset] = i;
            this.triangleIndices[triangleoffset + 1] = i + resolution;
            this.triangleIndices[triangleoffset + 2] = (i + 1) % resolution;
            triangleoffset += 3;

            this.triangleIndices[triangleoffset] = (i + 1) % resolution;
            this.triangleIndices[triangleoffset + 1] = i + resolution;
            this.triangleIndices[triangleoffset + 2] = ((i + 1) % resolution) + resolution;
            triangleoffset += 3;
        }

        this.numVertices = this.vertices.length / 3;
        this.numTriangles = this.triangleIndices.length / 3;
    }

    function Cone(resolution) {
        this.name = "cone";
        this.vertices = new Float32Array(3 * (resolution + 2));
        this.vertices[0] = 0.0;
        this.vertices[1] = 2.0;
        this.vertices[2] = 0.0;

        var radius = 1.0;
        var angle;
        var step = 6.283185307179586476925286766559 / resolution;

        var vertexoffset = 3;
        for (var i = 0; i < resolution; i++) {
            angle = step * i;
            this.vertices[vertexoffset] = radius * Math.cos(angle);
            this.vertices[vertexoffset + 1] = 0.0;
            this.vertices[vertexoffset + 2] = radius * Math.sin(angle);
            vertexoffset += 3;
        }

        this.vertices[vertexoffset] = 0.0;
        this.vertices[vertexoffset + 1] = 0.0;
        this.vertices[vertexoffset + 2] = 0.0;
        this.triangleIndices = new Uint16Array(3 * 2 * resolution);

        var triangleoffset = 0;
        for (var i = 0; i < resolution; i++) {
            this.triangleIndices[triangleoffset] = 0;
            this.triangleIndices[triangleoffset + 1] = 1 + (i % resolution);
            this.triangleIndices[triangleoffset + 2] = 1 + ((i + 1) % resolution);
            triangleoffset += 3;
        }

        for (var i = 0; i < resolution; i++) {
            this.triangleIndices[triangleoffset] = resolution + 1;
            this.triangleIndices[triangleoffset + 1] = 1 + (i % resolution);
            this.triangleIndices[triangleoffset + 2] = 1 + ((i + 1) % resolution);
            triangleoffset += 3;
        }

        this.numVertices = this.vertices.length / 3;
        this.numTriangles = this.triangleIndices.length / 3;
    }

    function Cube() {
        this.name = "cube";
        this.vertices = new Float32Array([
            -1.0, -1.0, 1.0,
            1.0, -1.0, 1.0,
            -1.0, 1.0, 1.0,
            1.0, 1.0, 1.0,
            -1.0, -1.0, -1.0,
            1.0, -1.0, -1.0,
            -1.0, 1.0, -1.0,
            1.0, 1.0, -1.0
        ]);

        this.triangleIndices = new Uint16Array([
            0, 1, 2, 2, 1, 3,
            5, 4, 7, 7, 4, 6,
            4, 0, 6, 6, 0, 2,
            1, 5, 3, 3, 5, 7,
            2, 3, 6, 6, 3, 7,
            4, 5, 0, 0, 5, 1
        ]);

        this.numVertices = this.vertices.length / 3;
        this.numTriangles = this.triangleIndices.length / 3;

    }

    function Triangle() {
        this.name = "Triangle";
        this.vertices = new Float32Array([0, 0, 0, 0.5, 0, -1, -0.5, 0, -1]);
        this.triangleIndices = new Uint16Array([0, 1, 2]);
        this.numVertices = 3;
        this.numTriangles = 1;
    }

    function Track(track) {
        this.name = "Track";

        var nv = track.leftCurb.length / 3 - 1;
        this.vertices = new Float32Array(nv * 2 * 3);

        var vertexOffset = 0;
        for (var i = 0; i < nv; ++i) {
            var v = track.leftCurb.slice(i * 3, i * 3 + 3);
            this.vertices[vertexOffset + 0] = v[0];
            this.vertices[vertexOffset + 1] = v[1];
            this.vertices[vertexOffset + 2] = v[2];
            vertexOffset += 3;
        }

        for (var i = 0; i < nv; ++i) {
            var v = track.rightCurb.slice(i * 3, i * 3 + 3);
            this.vertices[vertexOffset + 0] = v[0];
            this.vertices[vertexOffset + 1] = v[1];
            this.vertices[vertexOffset + 2] = v[2];
            vertexOffset += 3;
        }

        this.triangleIndices = new Uint16Array(nv * 3 * 2);

        var triangleoffset = 0;
        for (var i = 0; i < nv; ++i) {
            this.triangleIndices[triangleoffset + 0] = i;
            this.triangleIndices[triangleoffset + 1] = (i + 1) % nv;
            this.triangleIndices[triangleoffset + 2] = nv + (i + 1) % nv;
            triangleoffset += 3;

            this.triangleIndices[triangleoffset + 0] = i;
            this.triangleIndices[triangleoffset + 1] = nv + (i + 1) % nv;
            this.triangleIndices[triangleoffset + 2] = nv + i;
            triangleoffset += 3;
        }

        this.numVertices = nv * 2;
        this.numTriangles = this.triangleIndices.length / 3;
    };

    function Quadrilateral(quad, scale) {
        var nv = 4;
        this.vertices = new Float32Array(nv * 3);
        for (var i = 0; i < nv * 3; ++i)
            this.vertices[i] = quad[i];

        this.triangleIndices = new Uint16Array(2 * 3);

        this.triangleIndices[0] = 0;
        this.triangleIndices[1] = 1;
        this.triangleIndices[2] = 2;

        this.triangleIndices[3] = 0;
        this.triangleIndices[4] = 2;
        this.triangleIndices[5] = 3;

        this.numVertices = 4;
        this.numTriangles = 2;
    };

    function Building(b) {
        this.name = "Building";
        var nv = b.outline.length / 4;
        this.vertices = new Float32Array(nv * 2 * 3);

        var vertexOffset = 0;
        for (var i = 0; i < nv; ++i) {
            var v = b.outline.slice(i * 4, i * 4 + 3);
            this.vertices[vertexOffset + 0] = v[0];
            this.vertices[vertexOffset + 1] = v[1];
            this.vertices[vertexOffset + 2] = v[2];
            vertexOffset += 3;
        }

        for (var i = 0; i < nv; ++i) {
            var v = b.outline.slice(i * 4, i * 4 + 3);
            this.vertices[vertexOffset + 0] = v[0];
            this.vertices[vertexOffset + 1] = b.outline[i * 4 + 3];
            this.vertices[vertexOffset + 2] = v[2];
            vertexOffset += 3;
        }

        this.triangleIndices = new Uint16Array(3 * (2 * nv + nv - 2));

        var triangleOffset = 0;
        for (var i = 0; i < nv; ++i) {
            this.triangleIndices[triangleOffset + 0] = i;
            this.triangleIndices[triangleOffset + 1] = (i + 1) % nv;
            this.triangleIndices[triangleOffset + 2] = nv + (i + 1) % nv;
            triangleOffset += 3;

            this.triangleIndices[triangleOffset + 0] = i;
            this.triangleIndices[triangleOffset + 1] = nv + (i + 1) % nv;
            this.triangleIndices[triangleOffset + 2] = nv + i;
            triangleOffset += 3;
        }

        for (var i = 0; i < (nv - 2); ++i) {
            this.triangleIndices[triangleOffset + 0] = nv;
            this.triangleIndices[triangleOffset + 1] = nv + (i + 1);
            this.triangleIndices[triangleOffset + 2] = nv + (i + 2) % nv;
            triangleOffset += 3;
        }

        this.numVertices = nv * 2;
        this.numTriangles = this.triangleIndices.length / 3;
    };

    function Cabin() {
        this.name = "Cabin";

        this.vertices = new Float32Array([
            -1, -1, 0, 1, -1, 0, 1, -0.5, 0, 0.3, -0.4, 0, 0.26, -0.44, 0, -0.31, -0.44, 0, -0.36, -0.40, 0.0, -1, -0.44, 0.0, // base 8
            -1, 0.6, 0, -0.4, 0.83, 0.0, 0.36, 0.83, 0.0, 1, 0.6, 0.0, 1, 1, 0, -1, 1, 0, // upper 14
            -0.97, -0.8, -0.1, -0.35, -0.8, -0.1, -0.4, -0.5, -0.1, -0.95, -0.52, -0.1, // 18
            -0.3, -1, -0.2, 0.3, -1, -0.2, 0.2, -0.7, -0.2, -0.2, -0.7, -0.2, //22
            -1, -0.96, -0.3, -0.9, -0.7, -0.3, -0.69, -0.54, -0.3, -0.5, -0.5, -0.3, -0.29, -0.55, -0.3, -0.14, -0.66, -0.3, -0.043, -0.8, -0.3, -0.006, -1, -0.3,
            -1, -0.67, -0.3, -0.91, -0.58, -0.3, -0.72, -0.45, -0.3, -0.5, -0.4, -0.3, -0.25, -0.46, -0.3, -0.075, -0.59, -0.3, 0.050, -0.78, -0.3, 0.1, -1.0, -0.3
        ]);
        var nv = this.vertices.length / 3

        this.triangleIndices = new Uint16Array([
            0, 6, 7, 0, 5, 6, 0, 4, 5, 0, 1, 4, 1, 2, 4, 2, 3, 4,
            8, 9, 13, 9, 12, 13, 9, 10, 12, 10, 11, 12,
            14, 15, 17, 15, 16, 17,
            18, 19, 21, 19, 20, 21,
            22, 23, 30, 23, 24, 31, 24, 25, 32, 25, 26, 33, 26, 27, 34, 27, 28, 35, 28, 29, 36,
            23, 31, 30, 24, 32, 31, 25, 33, 32, 26, 34, 33, 27, 35, 34, 28, 36, 35, 29, 37, 36
        ]);

        this.vertex_color = new Float32Array([
            0.4, 0.2, 0.2, 1, 0.4, 0.2, 0.2, 1, 0.4, 0.3, 0.3, 1, 0.4, 0.3, 0.3, 1, 0.4, 0.3, 0.3, 1, 0.4, 0.3, 0.3, 1, 0.4, 0.3, 0.3, 1, 0.4, 0.3, 0.3, 1,
            0.4, 0.4, 0.4, 1, 0.4, 0.4, 0.4, 1, 0.4, 0.4, 0.4, 1, 0.4, 0.4, 0.4, 1, 0.5, 0.4, 0.4, 1, 0.5, 0.4, 0.4, 1,
            0.3, 0.1, 0.0, 1, 0.3, 0.1, 0.0, 1, 0.3, 0.1, 0.0, 1, 0.3, 0.1, 0.0, 1,
            0.3, 0.1, 0.1, 1, 0.3, 0.1, 0.1, 1, 0.3, 0.1, 0.1, 1, 0.3, 0.1, 0.1, 1,
            0.5, 0.1, 0.1, 1, 0.5, 0.1, 0.1, 1, 0.5, 0.1, 0.1, 1, 0.5, 0.1, 0.1, 1, 0.5, 0.1, 0.1, 1, 0.5, 0.1, 0.1, 1, 0.5, 0.1, 0.1, 1, 0.5, 0.1, 0.1, 1,
            0.4, 0.0, 0.0, 1, 0.4, 0.0, 0.0, 1, 0.4, 0.0, 0.0, 1, 0.4, 0.0, 0.0, 1, 0.4, 0.0, 0.0, 1, 0.4, 0.0, 0.0, 1, 0.4, 0.0, 0.0, 1, 0.4, 0.0, 0.0, 1,
        ]);

        this.numVertices = nv;
        this.numTriangles = nv - 2;
    };

    function RearMirror() {
        this.name = "RearMirror";
        this.vertices = new Float32Array([
            -0.45, 0.38, 0, 0.453, 0.388, 0, 0.369, 0.722, 0, -0.356, 0.722, 0,
            -0.044, 0.725, 0.0, 0.05, 0.725, 0, 0.09, 0.83, 0, -0.09, 0.83, 0.0,
        ]);
        var nv = this.vertices.length / 3

        this.triangleIndices = new Uint16Array([
            0, 1, 2, 0, 2, 3,
            4, 5, 6, 4, 6, 7,
        ]);

        this.vertex_color = new Float32Array([
            1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1,
            1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1,
        ]);

        this.numVertices = nv;
        this.numTriangles = nv - 2;
    }

    function Windshield() {
        this.name = "Windshield";

        this.vertices = new Float32Array([
            -1, 0.4, 0, -0.4, 0.63, 0.0, 0.36, 0.63, 0.0, 1, 0.4, 0.0, 1, 0.6, 0.0, 0.36, 0.83, 0.0, -0.4, 0.83, 0.0, -1, 0.6, 0,
            1, -0.5, 0, 0.3, -0.4, 0, 0.26, -0.44, 0, -0.31, -0.44, 0, -0.36, -0.40, 0.0, -1, -0.44, 0.0, -1, 0.6, 0, -0.4, 0.83, 0.0, 0.36, 0.83, 0.0, 1, 0.6, 0.0,
        ]);
        var nv = this.vertices.length / 3

        this.triangleIndices = new Uint16Array([
            0, 1, 7, 1, 6, 7, 1, 2, 5, 1, 5, 6, 2, 3, 4, 2, 4, 5,
            8, 9, 17, 9, 16, 17, 9, 10, 16, 10, 11, 16, 11, 12, 16, 12, 15, 16, 12, 13, 14, 12, 14, 15
        ]);

        this.vertex_color = new Float32Array([
            0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1,
            0.5, 0.5, 0.5, 0.1, 0.5, 0.5, 0.5, 0.1, 0.5, 0.5, 0.5, 0.1, 0.5, 0.5, 0.5, 0.1, 0.5, 0.5, 0.5, 0.1, 0.5, 0.5, 0.5, 0.1, 0, 0, 0, 0.1, 0, 0, 0, 0.1, 0, 0, 0, 0.1, 0, 0, 0, 0.1
        ]);

        this.numVertices = nv;
        this.numTriangles = nv - 2;
    };

    function Light(geometry, color) {
        if (!geometry) this.geometry = new Vector3(0.0, -1.0, 0.0);
        else this.geometry = geometry;
        if (!color) this.color = [1.0, 1.0, 1.0, 1.0];
        else this.color = color;
    }

    function Lamp(position, light) {
        this.position = position;
        this.light = light;
    }

    function Tunnel(t) {
        this.name = "Tunnel";
        var nv = t.leftCurb.length / 3;
        this.vertices = new Float32Array(nv * 7 * 3);
        var vertexOffset = 0;

        for (var strip = 0; strip < 7; ++strip)
            for (var i = 0; i < nv; ++i) {
                var alpha = strip / 6.0 * 3.14;
                var lambda = (Math.cos(alpha) + 1) / 2.0;
                var ls = Vector3.fromArray(t.leftCurb.slice(i * 3, i * 3 + 3)),
                    rs = Vector3.fromArray(t.rightCurb.slice(i * 3, i * 3 + 3));
                var r = Vector3.sub(rs, ls);
                var v = Vector3.lerp(rs, ls, lambda);
                this.vertices[vertexOffset + 0] = v.x;
                this.vertices[vertexOffset + 1] = t.height * Math.sin(alpha);
                this.vertices[vertexOffset + 2] = v.z;
                vertexOffset += 3;
            }

        this.triangleIndices = new Uint16Array((nv - 1) * 3 * 12);

        var triangleoffset = 0;
        for (var strip = 0; strip < 7; ++strip)
            for (var i = 0; i < nv - 1; ++i) {
                this.triangleIndices[triangleoffset + 0] = strip * nv + i
                this.triangleIndices[triangleoffset + 1] = strip * nv + (i + 1) % nv;
                this.triangleIndices[triangleoffset + 2] = (strip + 1) * nv + (i + 1) % nv;
                triangleoffset += 3;

                this.triangleIndices[triangleoffset + 0] = strip * nv + i
                this.triangleIndices[triangleoffset + 1] = (strip + 1) * nv + (i + 1) % nv;
                this.triangleIndices[triangleoffset + 2] = (strip + 1) * nv + i;
                triangleoffset += 3;
            }

        this.numVertices = this.vertices.length / 3;;
        this.numTriangles = this.triangleIndices.length / 3;
    };

    function SpotLight() {
        this.pos = [];
        this.dir = [];
        this.posViewSpace = [];
        this.dirViewSpace = [];
        this.cutOff = [];
        this.fallOff = [];
    }

    function TexturedTrack(track, scale) {
        this.name = "TexturedTrack";

        var nv = track.leftCurb.length / 3;

        /* create vertex position */
        this.vertices = new Float32Array((nv + 1) * 2 * 3);

        var vertexOffset = 0;
        for (var i = 0; i < nv + 1; ++i) {
            var v = track.leftCurb.slice(i * 3, i * 3 + 3);
            this.vertices[vertexOffset + 0] = v[0];
            this.vertices[vertexOffset + 1] = v[1];
            this.vertices[vertexOffset + 2] = v[2];
            vertexOffset += 3;
        }

        for (var i = 0; i < nv + 1; ++i) {
            var v = track.rightCurb.slice(i * 3, i * 3 + 3);
            this.vertices[vertexOffset + 0] = v[0];
            this.vertices[vertexOffset + 1] = v[1];
            this.vertices[vertexOffset + 2] = v[2];
            vertexOffset += 3;
        }

        /* create texture coordinates */
        this.textureCoord = new Float32Array((nv + 1) * 2 * 2);
        var d = 0.0;
        var v = track.leftCurb.slice(0, 3);
        var lp = [v[0], v[1], v[2]];

        vertexOffset = 0;
        for (var i = 0; i < nv + 1; ++i) {
            lp = v;
            var tempi = i % nv;
            var v = track.leftCurb.slice(tempi * 3, tempi * 3 + 3);
            d = d + Math.sqrt((lp[0] - v[0]) * (lp[0] - v[0]) + (lp[1] - v[1]) * (lp[1] - v[1]) + (lp[2] - v[2]) * (lp[2] - v[2]));
            this.textureCoord[vertexOffset + 0] = 0.0;
            this.textureCoord[vertexOffset + 1] = d * scale;
            vertexOffset += 2;
        }

        d = 0.0;
        v = track.leftCurb.slice(0, 3);
        lp = [v[0], v[1], v[2]];
        for (var i = 0; i < nv + 1; ++i) {
            lp = v;
            var tempi = i % nv;
            var v = track.leftCurb.slice(tempi * 3, tempi * 3 + 3);
            d = d + Math.sqrt((lp[0] - v[0]) * (lp[0] - v[0]) + (lp[1] - v[1]) * (lp[1] - v[1]) + (lp[2] - v[2]) * (lp[2] - v[2]));
            this.textureCoord[vertexOffset + 0] = 1.0;
            this.textureCoord[vertexOffset + 1] = d * scale;
            vertexOffset += 2;
        }

        this.triangleIndices = new Uint16Array(nv * 3 * 2);

        var triangleoffset = 0;
        for (var i = 0; i < nv; ++i) {
            this.triangleIndices[triangleoffset + 0] = i;
            this.triangleIndices[triangleoffset + 1] = i + 1;
            this.triangleIndices[triangleoffset + 2] = (nv + 1) + i + 1;
            triangleoffset += 3;

            this.triangleIndices[triangleoffset + 0] = i;
            this.triangleIndices[triangleoffset + 1] = (nv + 1) + i + 1;
            this.triangleIndices[triangleoffset + 2] = (nv + 1) + i;
            triangleoffset += 3;
        }

        this.numVertices = nv;
        this.numTriangles = this.triangleIndices.length / 3;
    }

    function TexturedFacades(b, scale) {
        this.name = "TexturedFacades";

        var nv = b.outline.length / 4;
        this.vertices = new Float32Array((nv + 1) * 2 * 3);

        var vertexOffset = 0;
        for (var i = 0; i < nv; ++i) {
            var v = b.outline.slice(i * 4, i * 4 + 3);
            this.vertices[vertexOffset + 0] = v[0];
            this.vertices[vertexOffset + 1] = v[1];
            this.vertices[vertexOffset + 2] = v[2];
            vertexOffset += 3;
        }

        {
            var v = b.outline.slice(0, 3);
            this.vertices[vertexOffset + 0] = v[0];
            this.vertices[vertexOffset + 1] = v[1];
            this.vertices[vertexOffset + 2] = v[2];
            vertexOffset += 3;
        }


        for (var i = 0; i < nv; ++i) {
            var v = b.outline.slice(i * 4, i * 4 + 3);
            this.vertices[vertexOffset + 0] = v[0];
            this.vertices[vertexOffset + 1] = b.outline[i * 4 + 3];
            this.vertices[vertexOffset + 2] = v[2];
            vertexOffset += 3;
        }

        {
            var v = b.outline.slice(0, 3);
            this.vertices[vertexOffset + 0] = v[0];
            this.vertices[vertexOffset + 1] = b.outline[3];
            this.vertices[vertexOffset + 2] = v[2];
            vertexOffset += 3;
        }

        this.textureCoord = new Float32Array((nv + 1) * 2 * 2);
        var d = 0.0;
        var v = b.outline.slice(0, 3);
        var lp = [v[0], v[1], v[2]];
        vertexOffset = 0;
        for (var i = 0; i < nv + 1; ++i) {
            var index = i % nv;
            var v = b.outline.slice(index * 4, index * 4 + 3);
            d = d + Math.sqrt((lp[0] - v[0]) * (lp[0] - v[0]) + (lp[1] - v[1]) * (lp[1] - v[1]) + (lp[2] - v[2]) * (lp[2] - v[2]));
            lp = v;
            this.textureCoord[vertexOffset + 0] = d / 5;
            this.textureCoord[vertexOffset + 1] = 0;

            this.textureCoord[(nv + 1) * 2 + vertexOffset + 0] = d / 5;
            this.textureCoord[(nv + 1) * 2 + vertexOffset + 1] = b.outline[index * 4 + 3] / 5;

            vertexOffset += 2;
        }

        this.triangleIndices = new Uint16Array(3 * (2 * nv));


        var triangleOffset = 0;
        for (var i = 0; i < nv; ++i) {
            this.triangleIndices[triangleOffset + 0] = i;
            this.triangleIndices[triangleOffset + 1] = i + 1;
            this.triangleIndices[triangleOffset + 2] = nv + 1 + i + 1;
            triangleOffset += 3;

            this.triangleIndices[triangleOffset + 0] = i;
            this.triangleIndices[triangleOffset + 1] = nv + 1 + i + 1;
            this.triangleIndices[triangleOffset + 2] = nv + 1 + i;
            triangleOffset += 3;
        }


        this.numVertices = (nv + 1) * 2;
        this.numTriangles = this.triangleIndices.length / 3;
    };

    function TexturedRoof(b, scale) {
        this.name = "TexturedRoof";

        var nv = b.outline.length / 4;
        this.vertices = new Float32Array(nv * 2 * 3);

        var vertexOffset = 0;

        for (var i = 0; i < nv; ++i) {
            var v = b.outline.slice(i * 4, i * 4 + 3);
            this.vertices[vertexOffset + 0] = v[0];
            this.vertices[vertexOffset + 1] = b.outline[i * 4 + 3];
            this.vertices[vertexOffset + 2] = v[2];
            vertexOffset += 3;
        }

        var min_u = this.vertices[0];
        var max_u = this.vertices[0];
        var min_v = this.vertices[2];
        var max_v = this.vertices[2];
        for (var i = 0; i < nv; ++i) {
            var curr_u = this.vertices[3 * i];
            var curr_v = this.vertices[3 * i + 2];
            if (curr_u > max_u) {
                max_u = curr_u;
            } else
            if (curr_u < min_u) {
                min_u = curr_u;
            }

            if (curr_v > max_v) {
                max_v = curr_v;
            } else
            if (curr_v < min_v) {
                min_v = curr_v;
            }
        }
        max_u = max_u - min_u;
        max_v = max_v - min_v;


        vertexOffset = 0;
        this.textureCoord = new Float32Array(nv * 2);
        for (var i = 0; i < nv; ++i) {
            this.textureCoord[vertexOffset + 0] = scale * (this.vertices[3 * i] - min_u) / max_u;
            this.textureCoord[vertexOffset + 1] = scale * (this.vertices[3 * i + 2] - min_v) / max_v;
            vertexOffset += 2;
        }

        this.textureCoord[0] = 0.0;
        this.textureCoord[1] = 0.0;
        this.textureCoord[2] = 1.0;
        this.textureCoord[3] = 0.0;
        this.textureCoord[4] = 1.0;
        this.textureCoord[5] = 1.0;
        this.textureCoord[6] = 0.0;
        this.textureCoord[7] = 1.0;


        this.triangleIndices = new Uint16Array(3 * (nv - 2));

        var triangleOffset = 0;
        /* triangles for the roof */
        for (var i = 0; i < (nv - 2); ++i) {
            this.triangleIndices[triangleOffset + 0] = 0;
            this.triangleIndices[triangleOffset + 1] = i + 1;
            this.triangleIndices[triangleOffset + 2] = (i + 2) % nv;
            triangleOffset += 3;
        }

        this.numVertices = nv;
        this.numTriangles = this.triangleIndices.length / 3;
    };

    function TexturedQuadrilateral(quad, tex_coords) {
        //this.name = "Ground";

        var nv = 4;
        this.vertices = new Float32Array(nv * 3);
        for (var i = 0; i < nv * 3; ++i)
            this.vertices[i] = quad[i];

        this.textureCoord = new Float32Array(nv * 2);
        for (var i = 0; i < nv * 2; ++i)
            this.textureCoord[i] = tex_coords[i];

        this.triangleIndices = new Uint16Array(2 * 3);


        this.triangleIndices[0] = 0;
        this.triangleIndices[1] = 1;
        this.triangleIndices[2] = 2;

        this.triangleIndices[3] = 0;
        this.triangleIndices[4] = 2;
        this.triangleIndices[5] = 3;


        this.numVertices = 4;
        this.numTriangles = 2;
    };


    App.define("primitive", {
        Cylinder: Cylinder,
        Ring: Ring,
        CircularStreet: CircularStreet,
        Cone: Cone,
        Cube: Cube,
        Triangle: Triangle,
        Track: Track,
        Quadrilateral: Quadrilateral,
        Building: Building,
        Cabin: Cabin,
        RearMirror: RearMirror,
        Windshield: Windshield,
        Light: Light,
        Lamp: Lamp,
        Tunnel: Tunnel,
        SpotLight: SpotLight,
        TexturedTrack: TexturedTrack,
        TexturedFacades: TexturedFacades,
        TexturedRoof: TexturedRoof,
        TexturedQuadrilateral: TexturedQuadrilateral
    })
})